Automatización de la Selección de Visualizaciones de Datos para la Alfabetización de Datos - Visualización Interactiva Mockup
2024-2 · Máster universitario en Ciencia de datos (Data science)
Estudios de Informática, Multimedia y Telecomunicación
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn import datasets
from sklearn import preprocessing
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from imblearn.over_sampling import RandomOverSampler, SMOTE, ADASYN
from imblearn.under_sampling import RandomUnderSampler, TomekLinks, EditedNearestNeighbours
import matplotlib
import matplotlib.pyplot as plt
pd.set_option('display.max_columns', None)
seed = 100
%matplotlib inline
Se eligieron varios datasets de Eurostat sobre migración y población. En particular se han utilizado las bases de datos:
Las dos primeras bases de datos se obtuvieron del siguiente URL: https://ec.europa.eu/eurostat/web/migration-asylum/international-migration-citizenship/database La base de datos de población se obtuve de esta otra URL: https://ec.europa.eu/eurostat/databrowser/view/demo_gind/default/table?lang=en
import os
import pandas as pd
# Especificar la carpeta donde están los archivos
carpeta = r"C:\Users\34617\Documents\MASTER_DATA_SCIENCE\TFM\visualizacion interactiva\FINAL"
# Leer los archivos Excel
countrycodes = pd.read_excel(os.path.join(carpeta, "Country_codes_regions2.xlsx"))
countrybirthcodes = pd.read_excel(os.path.join(carpeta, "Country_codes_regions_birth2.xlsx"))
inmigration = pd.read_excel(os.path.join(carpeta, "Inmigrations.xlsx"))
emigration = pd.read_excel(os.path.join(carpeta, "Emigrations.xlsx"))
population = pd.read_excel(os.path.join(carpeta, "Population_indicators.xlsx"))
# Mostrar los DataFrames para verificar que se cargaron correctamente
print(countrycodes.describe(include='all'))
print(countrybirthcodes.describe(include='all'))
print(inmigration.describe(include='all'))
print(emigration.describe(include='all'))
print(population.describe(include='all'))
#print(countrycodes.head())
#print(countrybirthcodes.head())
#print(inmigration.head())
#print(emigration.head())
#print(population.head())
reporting_country reporting_country_name reporting_country_region \
count 285 286 286
unique 284 281 12
top TW Other Africa
freq 2 2 66
reporting_country_sub-region
count 286
unique 41
top Sub-Saharan Africa
freq 53
country_birth country_birth_name country_birth_region \
count 285 286 286
unique 284 281 12
top TW Other Africa
freq 2 2 66
country_birth_sub-region
count 286
unique 41
top Sub-Saharan Africa
freq 53
freq age agedef country_birth unit sex \
count 739696 739696 739696 736856 739696 739696
unique 1 27 2 251 1 3
top A TOTAL COMPLET TOTAL NR T
freq 739696 77872 378432 13912 739696 251304
mean NaN NaN NaN NaN NaN NaN
std NaN NaN NaN NaN NaN NaN
min NaN NaN NaN NaN NaN NaN
25% NaN NaN NaN NaN NaN NaN
50% NaN NaN NaN NaN NaN NaN
75% NaN NaN NaN NaN NaN NaN
max NaN NaN NaN NaN NaN NaN
reporting_country conteo_nulos Year Inmigrations
count 739696 739696.0 739696.000000 7.396960e+05
unique 27 NaN NaN NaN
top HR NaN NaN NaN
freq 136272 NaN NaN NaN
mean NaN 0.0 2018.500000 7.308314e+02
std NaN 0.0 2.291289 1.324543e+04
min NaN 0.0 2015.000000 0.000000e+00
25% NaN 0.0 2016.750000 0.000000e+00
50% NaN 0.0 2018.500000 0.000000e+00
75% NaN 0.0 2020.250000 9.000000e+00
max NaN 0.0 2022.000000 1.943445e+06
freq age agedef country_birth unit sex \
count 442168 442168 442168 440400 442168 442168
unique 1 26 2 249 1 3
top A TOTAL COMPLET TOTAL NR T
freq 442168 68176 269672 5352 442168 150200
mean NaN NaN NaN NaN NaN NaN
std NaN NaN NaN NaN NaN NaN
min NaN NaN NaN NaN NaN NaN
25% NaN NaN NaN NaN NaN NaN
50% NaN NaN NaN NaN NaN NaN
75% NaN NaN NaN NaN NaN NaN
max NaN NaN NaN NaN NaN NaN
reporting_country conteo_nulos Year Emigrations
count 442168 442168.0 442168.00000 442168.000000
unique 13 NaN NaN NaN
top IT NaN NaN NaN
freq 125600 NaN NaN NaN
mean NaN 0.0 2018.50000 298.117697
std NaN 0.0 2.29129 5058.903108
min NaN 0.0 2015.00000 0.000000
25% NaN 0.0 2016.75000 0.000000
50% NaN 0.0 2018.50000 0.000000
75% NaN 0.0 2020.25000 4.000000
max NaN 0.0 2022.00000 696866.000000
freq reporting_country Year AVG_TOTAL_POPULATION \
count 460 460 460.000000 4.100000e+02
unique 1 46 NaN NaN
top A AL NaN NaN
freq 460 10 NaN NaN
mean NaN NaN 2019.500000 3.941081e+07
std NaN NaN 2.875408 9.371651e+07
min NaN NaN 2015.000000 0.000000e+00
25% NaN NaN 2017.000000 1.930942e+06
50% NaN NaN 2019.500000 6.896632e+06
75% NaN NaN 2022.000000 1.741734e+07
max NaN NaN 2024.000000 4.483829e+08
DEATH FEMENINE_JAN JAN
count 4.100000e+02 4.300000e+02 4.600000e+02
unique NaN NaN NaN
top NaN NaN NaN
freq NaN NaN NaN
mean 4.026899e+05 1.919619e+07 3.922861e+07
std 1.001969e+06 4.692074e+07 9.325904e+07
min 0.000000e+00 0.000000e+00 0.000000e+00
25% 1.763300e+04 9.462492e+05 2.551962e+06
50% 6.048000e+04 3.356255e+06 6.823312e+06
75% 1.409748e+05 8.237585e+06 1.731352e+07
max 5.297333e+06 2.294311e+08 4.492066e+08
#verifico si hay valores nulos en los datasets importados de eurostar
nulos = inmigration.isna().any().any()
if nulos:
print("Algunos valores en el dataset inmigración son nulos")
# Mostrar las columnas que contienen valores nulos
columnas_con_nulos = inmigration.columns[inmigration.isna().any()]
print("Columnas con valores nulos en inmig:", columnas_con_nulos)
else:
print("Ningun valor en el dataset inmigración son nulos")
nulos = emigration.isna().any().any()
if nulos:
print("Algunos valores en el dataset emigracion son nulos")
# Mostrar las columnas que contienen valores nulos
columnas_con_nulos = emigration.columns[emigration.isna().any()]
print("Columnas con valores nulos en emig:", columnas_con_nulos)
else:
print("Ningun valor en el dataset emigracion son nulos")
nulos = population.isna().any().any()
if nulos:
print("Algunos valores en el dataset population son nulos")
# Mostrar las columnas que contienen valores nulos
columnas_con_nulos = population.columns[population.isna().any()]
print("Columnas con valores nulos en pp:", columnas_con_nulos)
else:
print("Ningun valor en el dataset population son nulos")
Algunos valores en el dataset inmigración son nulos Columnas con valores nulos en inmig: Index(['country_birth'], dtype='object') Algunos valores en el dataset emigracion son nulos Columnas con valores nulos en emig: Index(['country_birth'], dtype='object') Algunos valores en el dataset population son nulos Columnas con valores nulos en pp: Index(['AVG_TOTAL_POPULATION', 'DEATH', 'FEMENINE_JAN'], dtype='object')
# si hay nulos remplazo por UNKNOWN pero en population por 0
# Reemplazo valores nulos en country birth por UNKNOWN y vuelvo a comprobar
conteo_nulos = inmigration['country_birth'].isna().sum()
print(f"Cantidad de valores nulos en inmigration 'country_birth': {conteo_nulos}")
#print(conteos_columna)
columnas_a_reemplazar = ['country_birth'] # Lista de columnas que deseas modificar
inmigration[columnas_a_reemplazar] = inmigration[columnas_a_reemplazar].fillna('UNK')
conteo_nulos = inmigration['country_birth'].isna().sum()
print(f"Cantidad de valores nulos en 'country_birth': {conteo_nulos}")
# Reemplazo valores nulos en country birth por UNKNOWN y vuelvo a comprobar
#conteos_columna = emigration['country_birth'].value_counts(dropna=False)
conteo_nulos = emigration['country_birth'].isna().sum()
print(f"Cantidad de valores nulos en 'country_birth': {conteo_nulos}")
#print(conteos_columna)
columnas_a_reemplazar = ['country_birth'] # Lista de columnas que deseas modificar
emigration[columnas_a_reemplazar] = emigration[columnas_a_reemplazar].fillna('UNK')
conteo_nulos = emigration['country_birth'].isna().sum()
print(f"Cantidad de valores nulos en 'country_birth' tras remplazo: {conteo_nulos}")
#conteos_columna = emigration['country_birth'].value_counts(dropna=False)
#print(conteos_columna)
# Contar valores nulos en las columnas 'AVG_TOTAL_POPULATION', 'DEATH', 'FEMENINE_JAN'
conteo_nulos = population[['AVG_TOTAL_POPULATION', 'DEATH', 'FEMENINE_JAN']].isna().sum()
print(f"Cantidad de valores nulos antes del reemplazo:\n{conteo_nulos}")
# Reemplazar valores nulos en las columnas especificadas por 0
columnas_a_reemplazar = ['AVG_TOTAL_POPULATION', 'DEATH', 'FEMENINE_JAN']
population[columnas_a_reemplazar] = population[columnas_a_reemplazar].fillna(0)
# Contar valores nulos en las columnas 'AVG_TOTAL_POPULATION', 'DEATH', 'FEMENINE_JAN'
conteo_nulos = population[['AVG_TOTAL_POPULATION', 'DEATH', 'FEMENINE_JAN']].isna().sum()
print(f"Cantidad de valores nulos después del reemplazo:\n{conteo_nulos}")
Cantidad de valores nulos en inmigration 'country_birth': 2840 Cantidad de valores nulos en 'country_birth': 0 Cantidad de valores nulos en 'country_birth': 1768 Cantidad de valores nulos en 'country_birth': 0 Cantidad de valores nulos antes del reemplazo: AVG_TOTAL_POPULATION 50 DEATH 50 FEMENINE_JAN 30 dtype: int64
# Convertir la columna "Year" a entero en ambos DataFrames
inmigration["Year"] = inmigration["Year"].astype(int)
emigration["Year"] = emigration["Year"].astype(int)
inmigration = inmigration.drop(columns=["freq", "agedef", "unit","conteo_nulos"], errors="ignore")
emigration = emigration.drop(columns=["freq", "agedef", "unit","conteo_nulos"], errors="ignore")
# Paso 1: Uno inmigration con population y seleccionar columnas - en este caso nos interesa linkar por pais de nacimiento al estudiar las inmigraciones
# en relacion a la seguridad y poblacion del pais del que vienen (se ha tomado el pais de nacimiento como el de origen)
merged_data_inmi = (
inmigration
.merge(population, on=["Year", "reporting_country"], how="inner")
)
# Elimino la columna 'reporting_country_y' (que proviene de population_i)
#merged_data_inmi = merged_data_inmi.drop(columns=["reporting_country_y"])
# Asegurarse de que 'reporting_country_x' tenga el nombre correcto
#merged_data_inmi = merged_data_inmi.rename(columns={"reporting_country_x": "reporting_country"})
# Paso 2: uno con los países, regiones etc..
merged_data_inmi = (
merged_data_inmi
.merge(countrycodes, on="reporting_country", how="left")
.merge(countrybirthcodes, on="country_birth", how="left")
)
# Paso 3: Repetp el proceso para "emigration" pero linkar por el pais que reporta para ver la poblacion y muertes del pais de emigracion
merged_data_emig = (
emigration
.merge(population, on=["Year", "reporting_country"], how="inner")
)
# Elimino la columna 'country_birth_y' (que proviene de population_e)
#merged_data_emig = merged_data_emig.drop(columns=["country_birth_y"])
# Asegurarse de que 'country_birth_x' tenga el nombre correcto
#merged_data_emig = merged_data_emig.rename(columns={"country_birth_x": "country_birth"})
merged_data_emig = (
merged_data_emig
.merge(countrycodes, on="reporting_country", how="left")
.merge(countrybirthcodes, on="country_birth", how="left")
)
print(merged_data_inmi.columns)
print(merged_data_emig.columns)
# Poner todas las columnas en minusculas
merged_data_inmi.columns = merged_data_inmi.columns.str.lower()
# Poner todas las columnas en minusculas
merged_data_emig.columns = merged_data_emig.columns.str.lower()
#print(merged_data_inmi.columns)
#print(merged_data_emig.columns)
# orden
column_order_i = ['year','age', 'sex',
'reporting_country', 'reporting_country_name', 'reporting_country_region','reporting_country_sub-region',
'country_birth', 'country_birth_name', 'country_birth_region', 'country_birth_sub-region',
'inmigrations', 'avg_total_population', 'death', 'femenine_jan','jan' ]
column_order_e = ['year','age', 'sex',
'reporting_country', 'reporting_country_name', 'reporting_country_region','reporting_country_sub-region',
'country_birth','country_birth_name','country_birth_region', 'country_birth_sub-region',
'emigrations', 'avg_total_population', 'death', 'femenine_jan','jan']
# Reordenar las columnas
merged_data_inmi = merged_data_inmi[column_order_i]
# Reordenar las columnas
merged_data_emig = merged_data_emig[column_order_e]
print(merged_data_inmi.describe(include='all'))
print(merged_data_emig.describe(include='all'))
Index(['year', 'age', 'sex', 'reporting_country', 'reporting_country_name',
'reporting_country_region', 'reporting_country_sub-region',
'country_birth', 'country_birth_name', 'country_birth_region',
'country_birth_sub-region', 'inmigrations', 'avg_total_population',
'death', 'femenine_jan', 'jan'],
dtype='object')
Index(['year', 'age', 'sex', 'reporting_country', 'reporting_country_name',
'reporting_country_region', 'reporting_country_sub-region',
'country_birth', 'country_birth_name', 'country_birth_region',
'country_birth_sub-region', 'emigrations', 'avg_total_population',
'death', 'femenine_jan', 'jan'],
dtype='object')
year age sex reporting_country reporting_country_name \
count 742776.0 742776 742776 742776 742776
unique 8.0 27 3 27 27
top 2015.0 TOTAL T HR Croatia
freq 92847.0 78160 252344 136824 136824
mean NaN NaN NaN NaN NaN
std NaN NaN NaN NaN NaN
min NaN NaN NaN NaN NaN
25% NaN NaN NaN NaN NaN
50% NaN NaN NaN NaN NaN
75% NaN NaN NaN NaN NaN
max NaN NaN NaN NaN NaN
reporting_country_region reporting_country_sub-region country_birth \
count 742776 742776 742776
unique 1 4 251
top Europe Southern Europe TOTAL
freq 742776 392280 13912
mean NaN NaN NaN
std NaN NaN NaN
min NaN NaN NaN
25% NaN NaN NaN
50% NaN NaN NaN
75% NaN NaN NaN
max NaN NaN NaN
country_birth_name country_birth_region country_birth_sub-region \
count 742776 742776 742776
unique 250 12 41
top Other Europe Sub-Saharan Africa
freq 20112 179840 136152
mean NaN NaN NaN
std NaN NaN NaN
min NaN NaN NaN
25% NaN NaN NaN
50% NaN NaN NaN
75% NaN NaN NaN
max NaN NaN NaN
inmigrations avg_total_population death femenine_jan \
count 7.427760e+05 7.427760e+05 7.427760e+05 7.427760e+05
unique NaN NaN NaN NaN
top NaN NaN NaN NaN
freq NaN NaN NaN NaN
mean 7.278536e+02 1.649419e+07 1.772644e+05 8.431840e+06
std 1.321802e+04 2.067951e+07 2.254252e+05 1.060170e+07
min 0.000000e+00 3.749400e+04 2.490000e+02 1.881300e+04
25% 0.000000e+00 2.842639e+06 4.084000e+04 1.544399e+06
50% 0.000000e+00 5.276968e+06 5.702300e+04 2.609187e+06
75% 9.000000e+00 1.734487e+07 1.533630e+05 8.701077e+06
max 1.943445e+06 8.379798e+07 1.066341e+06 4.217034e+07
jan
count 7.427760e+05
unique NaN
top NaN
freq NaN
mean 1.648687e+07
std 2.068275e+07
min 3.736600e+04
25% 2.859077e+06
50% 5.258317e+06
75% 1.728216e+07
max 8.323712e+07
year age sex reporting_country reporting_country_name \
count 443912.0 443912 443912 443912 443912
unique 8.0 26 3 13 13
top 2015.0 TOTAL T IT Italy
freq 55489.0 68432 150776 126152 126152
mean NaN NaN NaN NaN NaN
std NaN NaN NaN NaN NaN
min NaN NaN NaN NaN NaN
25% NaN NaN NaN NaN NaN
50% NaN NaN NaN NaN NaN
75% NaN NaN NaN NaN NaN
max NaN NaN NaN NaN NaN
reporting_country_region reporting_country_sub-region country_birth \
count 443912 443912 443912
unique 1 4 249
top Europe Southern Europe TOTAL
freq 443912 267952 5352
mean NaN NaN NaN
std NaN NaN NaN
min NaN NaN NaN
25% NaN NaN NaN
50% NaN NaN NaN
75% NaN NaN NaN
max NaN NaN NaN
country_birth_name country_birth_region country_birth_sub-region \
count 443912 443912 443912
unique 248 12 41
top Other Europe Sub-Saharan Africa
freq 6136 105840 87528
mean NaN NaN NaN
std NaN NaN NaN
min NaN NaN NaN
25% NaN NaN NaN
50% NaN NaN NaN
75% NaN NaN NaN
max NaN NaN NaN
emigrations avg_total_population death femenine_jan \
count 443912.000000 4.439120e+05 443912.000000 4.439120e+05
unique NaN NaN NaN NaN
top NaN NaN NaN NaN
freq NaN NaN NaN NaN
mean 296.952286 2.163009e+07 237046.914922 1.110266e+07
std 5048.989940 2.612338e+07 287879.408046 1.340806e+07
min 0.000000 3.749400e+04 249.000000 1.881300e+04
25% 0.000000 2.108079e+06 23261.000000 1.049039e+06
50% 0.000000 2.877325e+06 41776.000000 1.563839e+06
75% 4.000000 5.901367e+07 615261.000000 3.021118e+07
max 696866.000000 6.022960e+07 740317.000000 3.106718e+07
jan
count 4.439120e+05
unique NaN
top NaN
freq NaN
mean 2.163589e+07
std 2.613781e+07
min 3.736600e+04
25% 2.107180e+06
50% 2.895573e+06
75% 5.903013e+07
max 6.029550e+07
numerical_i = ['inmigrations', 'avg_total_population', 'death', 'femenine_jan','jan' ]
numerical_e = ['emigrations', 'avg_total_population', 'death', 'femenine_jan','jan' ]
categorical = ['year','age', 'sex',
'reporting_country', 'reporting_country_name', 'reporting_country_region','reporting_country_sub-region',
'country_birth','country_birth_name','country_birth_region', 'country_birth_sub-region']
# convierto las columnas numéricas a tipo numérico
for i in numerical_i:
merged_data_inmi[i] = pd.to_numeric(merged_data_inmi[i], errors='coerce')
for i in numerical_e:
merged_data_emig[i] = pd.to_numeric(merged_data_emig[i], errors='coerce')
# convierto las categoricas a category
for i in categorical:
merged_data_inmi[i] = merged_data_inmi[i].astype('category')
merged_data_emig[i] = merged_data_emig[i].astype('category')
#numero de filas y atributos
print("Shape of data (número de filas y atributos):", merged_data_inmi.shape , "\n")
print("Shape of data (número de filas y atributos):", merged_data_emig.shape , "\n")
#verifico si hay valores nulos
# Verificar si todas las columnas tienen valores nulos
nulos = merged_data_inmi.isna().any().any() # Usa .all() dos veces
if nulos:
print("Algunos valores en el dataset inmigración son nulos")
# Mostrar las columnas que contienen valores nulos
columnas_con_nulos = merged_data_inmi.columns[merged_data_inmi.isna().any()]
print("Columnas con valores nulos en inmig:", columnas_con_nulos)
else:
print("Ningun valor en el dataset inmigración son nulos")
nulos = merged_data_emig.isna().any().any() # Usa .all() dos veces
if nulos:
print("Algunos valores en el dataset emigracion son nulos")
# Mostrar las columnas que contienen valores nulos
columnas_con_nulos = merged_data_emig.columns[merged_data_emig.isna().any()]
print("Columnas con valores nulos en emig:", columnas_con_nulos)
else:
print("Ningun valor en el dataset emigracion son nulos")
Shape of data (número de filas y atributos): (742776, 16) Shape of data (número de filas y atributos): (443912, 16) Ningun valor en el dataset inmigración son nulos Ningun valor en el dataset emigracion son nulos
Tenemos 251 países de nacimiento de 12 regiones en la inmigración internacional a 27 países europeos. En la emigración son 249 países también de 12 regiones y 13 países europeos reportando.
# Guardar los DataFrames combinados en archivos Excel para poder usarlos en otros sistemas de visualizacion
merged_data_inmi.to_excel(os.path.join(carpeta, "Population_inmigration_indicators.xlsx"), index=False)
merged_data_emig.to_excel(os.path.join(carpeta, "Population_emigration_indicators.xlsx"), index=False)
merged_data_inmi.describe
merged_data_emig.describe
#creamos variables con los nombres de las variables categóricas y numéricas para inmigracion
tipos_de_datos_i = merged_data_inmi.dtypes
variables_categoricas_i = tipos_de_datos_i[tipos_de_datos_i == 'category'].index
variables_numericas_i = tipos_de_datos_i[tipos_de_datos_i != 'category'].index
#creamos variables con los nombres de las variables categóricas y numéricas para emigracion
tipos_de_datos_e = merged_data_emig.dtypes
variables_categoricas_e = tipos_de_datos_e[tipos_de_datos_e == 'category'].index
variables_numericas_e = tipos_de_datos_e[tipos_de_datos_e != 'category'].index
print('Categoricas en inmigracion: ',variables_categoricas_i)
print('Categoricas en emigracion: ',variables_categoricas_e)
print('Numericas en inmigracion: ',variables_numericas_i)
print('Numericas en emigracion: ',variables_numericas_e)
Categoricas en inmigracion: Index(['year', 'age', 'sex', 'reporting_country', 'reporting_country_name',
'reporting_country_region', 'reporting_country_sub-region',
'country_birth', 'country_birth_name', 'country_birth_region',
'country_birth_sub-region'],
dtype='object')
Categoricas en emigracion: Index(['year', 'age', 'sex', 'reporting_country', 'reporting_country_name',
'reporting_country_region', 'reporting_country_sub-region',
'country_birth', 'country_birth_name', 'country_birth_region',
'country_birth_sub-region'],
dtype='object')
Numericas en inmigracion: Index(['inmigrations', 'avg_total_population', 'death', 'femenine_jan', 'jan'], dtype='object')
Numericas en emigracion: Index(['emigrations', 'avg_total_population', 'death', 'femenine_jan', 'jan'], dtype='object')
#para las variables categóricas contamos las observaciones de cada grupo
import matplotlib.pyplot as plt
import seaborn as sns
# Crear el gráfico
for variable in variables_categoricas_i:
# Contar las observaciones de cada grupo (categoría)
counts = merged_data_inmi[variable].value_counts()
# Crear el gráfico de barras horizontal
plt.figure(figsize=(10, 6)) # Tamaño del gráfico
sns.barplot(x=counts.values, y=counts.index, orient='h', palette='viridis') # gráfico horizontal
plt.title(f'Conteo de Observaciones inmigracion para {variable}')
plt.xlabel('Número de Observaciones inmigracion')
plt.ylabel('Categorías inmigracion')
plt.show()
# Crear el gráfico
for variable in variables_categoricas_e:
# Contar las observaciones de cada grupo (categoría)
counts = merged_data_emig[variable].value_counts()
# Crear el gráfico de barras horizontal
plt.figure(figsize=(10, 6)) # Tamaño del gráfico
sns.barplot(x=counts.values, y=counts.index, orient='h', palette='viridis') # gráfico horizontal
plt.title(f'Conteo de Observaciones emigracion para {variable}')
plt.xlabel('Número de Observaciones emigracion')
plt.ylabel('Categorías emigracion')
plt.show()
#quitamos los datos totales del dataframe
filtered_data_inmi_wo_total = merged_data_inmi[(merged_data_inmi['age'] != 'TOTAL') &
(merged_data_inmi['sex'] != 'TOTAL') &
(merged_data_inmi['country_birth'] != 'TOTAL')]
filtered_data_emig_wo_total = merged_data_emig[(merged_data_emig['age'] != 'TOTAL') &
(merged_data_emig['sex'] != 'TOTAL') &
(merged_data_emig['country_birth'] != 'TOTAL')]
#Estadísticos de las variables numéricas
filtered_data_inmi_wo_total.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| inmigrations | 651808.0 | 1.808839e+02 | 3.978568e+03 | 0.0 | 0.0 | 0.0 | 5.000000e+00 | 1.255690e+06 |
| avg_total_population | 651808.0 | 1.696725e+07 | 2.095429e+07 | 37494.0 | 2842639.0 | 5276968.0 | 1.744150e+07 | 8.379798e+07 |
| death | 651808.0 | 1.827221e+05 | 2.287667e+05 | 249.0 | 41106.0 | 57023.0 | 1.686780e+05 | 1.066341e+06 |
| femenine_jan | 651808.0 | 8.675940e+06 | 1.074500e+07 | 18813.0 | 1544399.0 | 2609187.0 | 8.759554e+06 | 4.217034e+07 |
| jan | 651808.0 | 1.696073e+07 | 2.095909e+07 | 37366.0 | 2859077.0 | 5258317.0 | 1.740758e+07 | 8.323712e+07 |
| inmigrations_log | 651808.0 | 1.186878e+00 | 1.947274e+00 | 0.0 | 0.0 | 0.0 | 1.791759e+00 | 1.404320e+01 |
filtered_data_emig_wo_total.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| emigrations | 370752.0 | 7.092228e+01 | 8.935490e+02 | 0.0 | 0.0 | 0.0 | 2.000000e+00 | 7.247800e+04 |
| avg_total_population | 370752.0 | 2.300096e+07 | 2.664778e+07 | 2063531.0 | 2112076.0 | 2877325.0 | 5.913317e+07 | 6.022960e+07 |
| death | 370752.0 | 2.529519e+05 | 2.934875e+05 | 19689.0 | 24016.0 | 41776.0 | 6.331330e+05 | 7.403170e+05 |
| femenine_jan | 370752.0 | 1.181137e+07 | 1.367505e+07 | 1039839.0 | 1049485.0 | 1563839.0 | 3.036999e+07 | 3.106718e+07 |
| jan | 370752.0 | 2.300844e+07 | 2.666255e+07 | 2062874.0 | 2108977.0 | 2895573.0 | 5.923621e+07 | 6.029550e+07 |
| emigrations_log | 370752.0 | 8.968855e-01 | 1.692514e+00 | 0.0 | 0.0 | 0.0 | 1.098612e+00 | 1.119105e+01 |
#bloxplot para visualizar los estadísticos
plt.figure(figsize=(10,15))
# Un boxplot para cada variable numérica
for i, col in enumerate(variables_numericas_e):
plt.subplot(5,3,i+1)
filtered_data_emig_wo_total.boxplot(col)
plt.grid()
plt.tight_layout()
#bloxplot para visualizar los estadísticos
plt.figure(figsize=(10,15))
# Un boxplot para cada variable numérica
for i, col in enumerate(variables_numericas_i):
plt.subplot(5,3,i+1)
filtered_data_inmi_wo_total.boxplot(col)
plt.grid()
plt.tight_layout()
# Transformación logarítmica de la medida debido a lo dispersos que están los valores
filtered_data_inmi_wo_total['inmigrations_log'] = np.log1p(filtered_data_inmi_wo_total['inmigrations']) # log(1 + x) para evitar log(0)
filtered_data_emig_wo_total['emigrations_log'] = np.log1p(filtered_data_emig_wo_total['emigrations'])
# Ver los primeros registros después de la transformación
print(filtered_data_inmi_wo_total[['inmigrations', 'inmigrations_log']].head())
print(filtered_data_emig_wo_total[['emigrations', 'emigrations_log']].head())
inmigrations inmigrations_log
1494 0 0.000000
1495 0 0.000000
1496 0 0.000000
1497 0 0.000000
1498 2 1.098612
emigrations emigrations_log
13182 0 0.000000
13183 0 0.000000
13184 0 0.000000
13185 3 1.386294
13186 2 1.098612
C:\Users\34617\AppData\Local\Temp\ipykernel_19920\4165713483.py:2: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy C:\Users\34617\AppData\Local\Temp\ipykernel_19920\4165713483.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
import matplotlib.pyplot as plt
import seaborn as sns
# Histograma con escala logarítmica
plt.figure(figsize=(10, 6))
sns.histplot(filtered_data_inmi_wo_total['inmigrations_log'], kde=True)
plt.yscale('log') # Escala logarítmica para el eje y
plt.xlabel('Inmigraciones (log transformadas)')
plt.ylabel('Frecuencia')
plt.title('Distribución de Inmigraciones (Escala Logarítmica)')
plt.show()
Hemos tenido que hacer un histograma usando la funcion logarítmica pues como vemos en los boxplots la distribución no es en absoluto normal y existen muchos outliers que dificulta la visualización del histograma. Vemos que hay una asimetría a la izquierda y que más del 75% de las observaciones tiene valores de inmigración inferiores a 10.
plt.figure(figsize=(10, 6))
sns.boxplot(x=filtered_data_inmi_wo_total['inmigrations_log'])
plt.title('Boxplot de Inmigraciones (Transformación Logarítmica)')
plt.xlabel('Inmigraciones')
plt.show()
Vemos más claramente ahora un boxplot con el valor escalado de inmigraciones que hay muchos outliers. Realmente esto es interesante en esta visualización. Detectar estas anomalías o cuales son los factores geográficos o de edad que hacen que en algunos casos hay muchas más inmigraciones. Veamos cual sería la distribución eliminando del dataset los valores por debajo o igual a 10 en inmigraciones.
#filtramos por encima del cuartile 75% que nos daba 9 como valor en inmigraciones y 4 en emigraciones
filtered_data_inmi = filtered_data_inmi_wo_total[filtered_data_inmi_wo_total['inmigrations'] > 5]
filtered_data_emig = filtered_data_emig_wo_total[filtered_data_emig_wo_total['emigrations'] > 2]
plt.figure(figsize=(10, 6))
sns.histplot(filtered_data_inmi['inmigrations'], kde=False, bins=50)
plt.yscale('log')
plt.xlabel('Inmigraciones (>9)')
plt.ylabel('Frecuencia')
plt.title('Distribución de Inmigraciones (filtradas)')
plt.show()
import seaborn as sns
# Density plot
sns.kdeplot(filtered_data_inmi['inmigrations'])
<AxesSubplot: xlabel='inmigrations', ylabel='Density'>
filtered_data_inmi.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| inmigrations | 158235.0 | 7.436048e+02 | 8.048951e+03 | 6.00000 | 1.400000e+01 | 4.100000e+01 | 1.850000e+02 | 1.255690e+06 |
| avg_total_population | 158235.0 | 2.670599e+07 | 2.307056e+07 | 37494.00000 | 5.408320e+06 | 1.723162e+07 | 4.744382e+07 | 8.379798e+07 |
| death | 158235.0 | 2.768515e+05 | 2.519286e+05 | 249.00000 | 5.697900e+04 | 1.518850e+05 | 4.619540e+05 | 1.066341e+06 |
| femenine_jan | 158235.0 | 1.361830e+07 | 1.183113e+07 | 18813.00000 | 2.672110e+06 | 8.654043e+06 | 2.415219e+07 | 4.217034e+07 |
| jan | 158235.0 | 2.667996e+07 | 2.307006e+07 | 37366.00000 | 5.391369e+06 | 1.718108e+07 | 4.740080e+07 | 8.323712e+07 |
| inmigrations_log | 158235.0 | 4.151503e+00 | 1.811703e+00 | 1.94591 | 2.708050e+00 | 3.737670e+00 | 5.225747e+00 | 1.404320e+01 |
El histograma filtrado ahora puede visualizarse correctamente y nos muestra aún asimetría a la izquierda es decir gran concentración de observaciones con pocas inmigraciones y además es un histograma con ciertos intervalos vacíos o histograma discontinuo.
plt.figure(figsize=(10, 6))
sns.histplot(filtered_data_emig['emigrations'], kde=False, bins=50)
plt.yscale('log')
plt.xlabel('Inmigraciones (>4)')
plt.ylabel('Frecuencia')
plt.title('Distribución de Emigraciones (filtradas)')
plt.show()
sns.kdeplot(filtered_data_emig['emigrations'])
<AxesSubplot: xlabel='emigrations', ylabel='Density'>
El histograma filtrado de emigraciones igual que el de inmigraciones nos muestra aún asimetría a la izquierda es decir gran concentración de observaciones con pocas emigraciones y además es un histograma con ciertos intervalos vacíos o histograma discontinuo
# visualicemos la poblacin y muertes
# elimino duplicados basados en las columnas 'year', 'reporting_country'/'country_birth' 'deaths', y 'jan' pues la granularidad es diferente
merged_data_unique_i = merged_data_inmi.drop_duplicates(subset=['year', 'reporting_country', 'country_birth','death', 'jan'])
merged_data_unique_e = merged_data_emig.drop_duplicates(subset=['year', 'reporting_country', 'country_birth','death', 'jan'])
#print(merged_data_unique_i.head())
#print(merged_data_unique_e.head())
histogramas = ['death', 'jan']
fig, axs = plt.subplots(1, len(histogramas), figsize=(15, 5))
# Histograma para cada valor en la variable histogramas
for i, col in enumerate(histogramas):
axs[i].hist(merged_data_unique_i[col])
axs[i].set_title(f'Histograma inmi de {col}')
# Superponemos
plt.tight_layout()
# Y pintamos
plt.show()
# Histograma para cada valor en la variable histogramas
fig, axs = plt.subplots(1, len(histogramas), figsize=(15, 5))
for i, col in enumerate(histogramas):
axs[i].hist(merged_data_unique_e[col])
axs[i].set_title(f'Histograma emig de {col}')
# Superponemos
plt.tight_layout()
# Y pintamos
plt.show()
La distribución de la población (jan) y muertes (death) también es asimétrica a la izquierda y son coherentes los números entre ambas. También los rangos son discontinuas presentando huecos en el histograma.
Exploramos la relación de algunos de los atributos numéricos con las variables categóricas.
import pandas as pd
import plotly.express as px
# Crear gráfico de barras horizontales
fig = px.bar(
merged_data_unique_i,
x='jan', # Eje X: número de registros
y='reporting_country', # Eje Y: países
orientation='h', # Orientación horizontal
title="Ranking de población por 'reporting_country'",
labels={'jan': 'Población', 'reporting_country': 'País'}, # Etiquetas
template="plotly_white" , # Estilo del gráfico
category_orders={'reporting_country': merged_data_unique_i.sort_values('jan', ascending=False)['reporting_country'].tolist()}
)
# Ajustar el eje Y para que se muestren todas las etiquetas
fig.update_layout(
yaxis=dict(
tickmode='array', # Asegura que todas las categorías se muestren
tickvals=merged_data_unique_i['reporting_country'].tolist(), # Usa todas las categorías
ticktext=merged_data_unique_i['reporting_country'].tolist(), # Muestra las etiquetas completas
tickangle=0, # Para evitar que las etiquetas se corten
),
height=600, # Ajusta el tamaño del gráfico si es necesario
width=1000 # Ajusta el ancho si es necesario
)
# Mostrar el gráfico
fig.show()
import pandas as pd
import plotly.express as px
# Crear gráfico de barras horizontales
fig = px.bar(
merged_data_unique_i,
x='death', # Eje X: número de registros
y='reporting_country', # Eje Y: países
orientation='h', # Orientación horizontal
title="Ranking de Muertes por 'reporting_country'",
labels={'death': 'Muertes', 'reporting_country': 'País'}, # Etiquetas
template="plotly_white", # Estilo del gráfico
category_orders={'reporting_country': merged_data_unique_i.sort_values('jan', ascending=False)['reporting_country'].tolist()}
)
# Ajustar el eje Y para que se muestren todas las etiquetas
fig.update_layout(
yaxis=dict(
tickmode='array', # Asegura que todas las categorías se muestren
tickvals=merged_data_unique_i['reporting_country'].tolist(), # Usa todas las categorías
ticktext=merged_data_unique_i['reporting_country'].tolist(), # Muestra las etiquetas completas
tickangle=0, # Para evitar que las etiquetas se corten
),
height=600, # Ajusta el tamaño del gráfico si es necesario
width=1000 # Ajusta el ancho si es necesario
)
# Mostrar el gráfico
fig.show()
# Calcular la tasa de muertes por población (por cada 100,000 habitantes)
merged_data_unique_i['death_rate_per_100k'] = (merged_data_unique_i['death'] / merged_data_unique_i['jan']) * 100000
# Agrupar por 'reporting_country' y calcular el promedio de la tasa de muertes por población
data_aggregated = merged_data_unique_i.groupby('reporting_country_name')['death_rate_per_100k'].mean().reset_index()
# Ordenar los datos de mayor a menor
data_aggregated = data_aggregated.sort_values(by='death_rate_per_100k', ascending=False)
# Crear gráfico de barras horizontales
fig = px.bar(
data_aggregated,
x='death_rate_per_100k', # Eje X: tasa de muertes por población
y='reporting_country_name', # Eje Y: países
orientation='h', # Orientación horizontal
title="Tasa de muertes por población por 'reporting_country' (Ordenado de Mayor a Menor)",
labels={'death_rate_per_100k': 'Tasa de muertes por cada 100,000 habitantes', 'reporting_country_name': 'País'}, # Etiquetas
template="plotly_white", # Estilo del gráfico
category_orders={'reporting_country_name': data_aggregated.sort_values('death_rate_per_100k', ascending=False)['reporting_country_name'].tolist()}
)
# Ajustar el eje Y para que se muestren todas las etiquetas
fig.update_layout(
yaxis=dict(
tickmode='array', # Asegura que todas las categorías se muestren
tickvals=data_aggregated['reporting_country_name'].tolist(), # Usa todas las categorías
ticktext=data_aggregated['reporting_country_name'].tolist(), # Muestra las etiquetas completas
tickangle=0, # Para evitar que las etiquetas se corten
),
height=600, # Ajusta el tamaño del gráfico si es necesario
width=1000 # Ajusta el ancho si es necesario
)
# Mostrar el gráfico
fig.show()
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from scipy.stats import gaussian_kde
# Configuración inicial
countries = filtered_data_inmi['country_birth_sub-region'].unique() # Usamos filtered_data_inmi para las subregiones
fig = go.Figure()
# Recorremos cada subregión (country_birth_sub-region) y creamos una curva KDE desplazada
for i, country in enumerate(countries):
# Subconjunto de datos para cada país, filtrado por 'country_birth_sub-region' en filtered_data_inmi
subset = merged_data_unique_i[merged_data_unique_i['country_birth_sub-region'] == country]['inmigrations']
# Verificar que subset no esté vacío y tenga suficiente variabilidad
if subset.empty or subset.nunique() == 1: # Si solo hay un valor único, omitir este país
continue
# Calcular la densidad KDE
try:
kde = gaussian_kde(subset)
x_vals = np.linspace(subset.min(), subset.max(), 500) # Generar un rango de valores para evaluar KDE
y_vals = kde(x_vals)
# Desplazar cada curva verticalmente para que no se solapen
y_vals_shifted = y_vals + i * 0.2 # Ajusta el desplazamiento vertical aquí
# Agregar la curva al gráfico
fig.add_trace(go.Scatter(
x=x_vals,
y=y_vals_shifted,
mode='lines',
fill='tonexty', # Rellenar debajo de la curva
name=country # Nombre de la subregión
))
except Exception as e:
print(f"Error al calcular KDE para {country}: {e}")
continue
# Personalización del gráfico
fig.update_layout(
title="Ridgeline Plot de inmigraciones por 'country_birth_sub-region'",
xaxis_title="Inmigraciones",
yaxis=dict(
tickvals=[i * 0.2 for i in range(len(countries))], # Posición en el eje y para cada curva
ticktext=countries, # Etiquetas para cada subregión
showgrid=False, # No mostrar la cuadrícula en el eje Y
),
showlegend=False, # No mostrar la leyenda
template="plotly_white",
height=600,
width=1000
)
fig.show()
# getting necessary libraries
import plotly.graph_objects as go
import numpy as np
import pandas as pd
# getting the data
temp = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2016-weather-data-seattle.csv') # we retrieve the data from plotly's GitHub repository
temp['year'] = pd.to_datetime(temp['Date']).dt.year # we store the year in a separate column
# Since we do not want to plot 50+ lines, we only select some years to plot
year_list = [1950, 1960, 1970, 1980, 1990, 2000, 2010]
temp = temp[temp['year'].isin(year_list)]
# as we expect to plot histograms-like plots for each year, we group by year and mean temperature and aggregate with 'count' function
temp = temp.groupby(['year', 'Mean_TemperatureC']).agg({'Mean_TemperatureC': 'count'}).rename(columns={'Mean_TemperatureC': 'count'}).reset_index()
# the idea behind this ridgeline plot with Plotly is to add traces manually, each trace corresponding to a particular year's temperature distribution
# thus, we are to store each year's data (temperatures and their respective count) in seperate arrays or pd.series that we store in a dictionnary to retrieve them easily
array_dict = {} # instantiating an empty dictionnary
for year in year_list:
array_dict[f'x_{year}'] = temp[temp['year']==year]['Mean_TemperatureC'] # storing the temperature data for each year
array_dict[f'y_{year}'] = temp[temp['year']==year]['count'] # storing the temperature count for each year
array_dict[f'y_{year}'] = (array_dict[f'y_{year}'] - array_dict[f'y_{year}'].min()) \
/ (array_dict[f'y_{year}'].max() - array_dict[f'y_{year}'].min()) # we normalize the array (min max normalization)
# once all of this is done, we can create a plotly.graph_objects.Figure and add traces with fig.add_trace() method
# since we have stored the temperatures and their respective count for each year, we can plot scatterplots (go.Scatter)
# we thus iterate over year_list and create a 'blank line' that is placed at y = index, then the corresponding temperature count line
fig = go.Figure()
for index, year in enumerate(year_list):
fig.add_trace(go.Scatter(
x=[-20, 40], y=np.full(2, len(year_list)-index),
mode='lines',
line_color='white'))
fig.add_trace(go.Scatter(
x=array_dict[f'x_{year}'],
y=array_dict[f'y_{year}'] + (len(year_list)-index) + 0.4,
fill='tonexty',
name=f'{year}'))
# plotly.graph_objects' way of adding text to a figure
fig.add_annotation(
x=-20,
y=len(year_list)-index,
text=f'{year}',
showarrow=False,
yshift=10)
# here you can modify the figure and the legend titles
fig.update_layout(
title='Average temperature from 1950 until 2010 in Seattle',
showlegend=False,
xaxis=dict(title='Temperature in degree Celsius'),
yaxis=dict(showticklabels=False) # that way you hide the y axis ticks labels
)
fig.show()
import plotly.graph_objects as go
import numpy as np
import pandas as pd
from scipy.stats import gaussian_kde
# Configuración inicial
countries = filtered_data_inmi['country_birth_sub-region'].unique() # Usamos filtered_data_inmi para las subregiones
# Filtramos los datos para las subregiones y eliminamos valores nulos o extremos
filtered_data_inmi_bycb = filtered_data_inmi[filtered_data_inmi['country_birth_sub-region'].isin(countries)]
# Aseguramos que no tengamos valores cero antes de aplicar el logaritmo
filtered_data_inmi_bycb = filtered_data_inmi_bycb[filtered_data_inmi_bycb['inmigrations'] > 0]
# Creamos una nueva columna con el logaritmo de las inmigraciones
filtered_data_inmi_bycb['inmigrations_log'] = np.log(filtered_data_inmi_bycb['inmigrations'])
# Diccionario para almacenar los datos de inmigración logarítmica
array_dict = {}
for country in countries:
# Extraemos los datos correspondientes para cada subregión
subset = filtered_data_inmi_bycb[filtered_data_inmi_bycb['country_birth_sub-region'] == country]
# Realizamos una estimación de densidad con KDE para suavizar las distribuciones
kde = gaussian_kde(subset['inmigrations_log'], bw_method=0.2) # Ajustamos el parámetro de ancho de banda si es necesario
x_vals = np.linspace(subset['inmigrations_log'].min(), subset['inmigrations_log'].max(), 500) # Rango de valores logarítmicos
y_vals = kde(x_vals) # Evaluamos la densidad
# Normalizamos los valores para que las curvas no se solapen
y_vals_normalized = (y_vals - y_vals.min()) / (y_vals.max() - y_vals.min()) # Normalización
# Guardamos los valores de x e y para cada país
array_dict[f'x_{country}'] = x_vals
array_dict[f'y_{country}'] = y_vals_normalized + (len(countries)-np.where(countries == country)[0][0]) # Desplazamos las curvas
# Creamos la figura de Plotly
fig = go.Figure()
# Añadimos las curvas de cada país
for index, country in enumerate(countries):
# Añadimos una línea base blanca para separar las curvas
fig.add_trace(go.Scatter(
x=[-20, 40], y=np.full(2, len(countries)-index),
mode='lines',
line_color='white'))
# Añadimos la curva de inmigración logarítmica con su correspondiente relleno
fig.add_trace(go.Scatter(
x=array_dict[f'x_{country}'],
y=array_dict[f'y_{country}'],
fill='tonexty',
name=f'{country}'))
# Añadimos el nombre del país como anotación (fuera del gráfico para evitar solapamiento)
fig.add_annotation(
x=array_dict[f'x_{country}'][0], # Primer valor de 'x' de la curva
y=len(countries)-index + 0.3, # Ajustamos la posición para que no se solape
text=f'{country}',
showarrow=False,
font=dict(size=10),
yshift=10)
# Personalización del gráfico
fig.update_layout(
title='Distribución por país de nacimiento de inmigraciones (escala logarítmica)',
showlegend=False,
xaxis=dict(title='Inmigraciones (log)'),
yaxis=dict(showticklabels=False), # Ocultamos las etiquetas del eje Y
template="plotly_white", # Estilo blanco para el gráfico
height=800,
width=1000
)
fig.show()
Vemos claramente con el ridline por subregiones una asimetría a la derecha y valores outlier en Central Africa, Caribbean y Easter Europe. Más pronunciada en las dos primeras.
import plotly.graph_objects as go
import pandas as pd
# datos agrupados por subregión y año
filtered_data_inmi_grouped = filtered_data_inmi.groupby(['country_birth_sub-region', 'year']).agg({'inmigrations_log': 'sum'}).reset_index()
# una figura de Plotly
fig = go.Figure()
# todas las subregiones únicas
subregions = filtered_data_inmi_grouped['country_birth_sub-region'].unique()
# Definiré una paleta de colores para daltonismo se haría de este modo aprox.
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
# una línea para cada subregión con colores definidos arriba
for i, subregion in enumerate(subregions):
# Filtro los datos por subregión
subregion_data = filtered_data_inmi_grouped[filtered_data_inmi_grouped['country_birth_sub-region'] == subregion]
# un color de la paleta a la subregión
color = colors[i % len(colors)] # Aseguramos así que no se repitan colores si hay más subregiones que colores
# Añado la línea para esa subregión
fig.add_trace(go.Scatter(
x=subregion_data['year'],
y=subregion_data['inmigrations_log'],
mode='lines+markers',
name=subregion,
line=dict(color=color, width=2), # Ajustar el grosor de la línea
marker=dict(size=8) # Tamaño de los puntos
))
# Personalizacion del gráfico
fig.update_layout(
title='Evolución de las Inmigraciones por Subregión a lo largo del tiempo',
xaxis_title='Año',
yaxis_title='Inmigraciones (Log)',
template='plotly_white',
showlegend=True,
height=600,
width=1000
)
# Mostramos el gráfico
fig.show()
# Miramos ese pico alto entre 2021 y 2022 en Eastern Europe en más detalle -> drill down
import plotly.graph_objects as go
# Agrupar los datos por 'country_birth_sub-region', 'country_birth_name' y 'year'
filtered_data_inmi_grouped = filtered_data_inmi.groupby(['country_birth_sub-region','country_birth_name', 'year']).agg({'inmigrations_log': 'sum'}).reset_index()
# Filtrar los datos para Eastern Europe
filtered_data_inmi_grouped_EAE = filtered_data_inmi_grouped[filtered_data_inmi_grouped['country_birth_sub-region'] == 'Eastern Europe']
filtered_data_inmi_grouped_EAE.loc[:, 'country_birth_name'] = filtered_data_inmi_grouped_EAE['country_birth_name'].astype(str)
# Crear la figura
fig = go.Figure()
#filtered_data_inmi_grouped_EAE = merged_data_inmi[merged_data_inmi['country_birth_sub-region'] == 'Eastern Europe']
countries_EAE = filtered_data_inmi_grouped_EAE['country_birth_name'].astype(str).unique()
print("Países de Eastern Europe:", countries_EAE)
# Iterar por cada país en Eastern Europe y agregar una línea al gráfico
countries = filtered_data_inmi_grouped_EAE['country_birth_name'].astype(str).unique()
print(countries)
for country in countries:
# Filtrar los datos para el país actual
country_data = filtered_data_inmi_grouped_EAE[filtered_data_inmi_grouped_EAE['country_birth_name'] == country]
# Agregar la traza de la línea para este país
fig.add_trace(go.Scatter(
x=country_data['year'], # Eje X: años
y=country_data['inmigrations_log'], # Eje Y: inmigraciones (log)
mode='lines+markers', # Usar líneas y marcadores
name=country # Nombre de la traza es el país
))
# Personalización del gráfico
fig.update_layout(
title='Inmigraciones por país de nacimiento en Eastern Europe',
xaxis=dict(
title='Año',
tickmode='linear', # Asegurar que los años estén distribuidos linealmente
dtick=1 # Mostrar todos los años
),
yaxis=dict(
title='Inmigraciones (log)',
rangemode='tozero' # Asegurar que el eje Y comience en 0
),
legend_title='País de Nacimiento',
template='plotly_white', # Estilo de gráfico blanco
height=600, # Ajuste de tamaño
width=1000 # Ajuste de tamaño
)
# Mostrar el gráfico
fig.show()
C:\Users\34617\AppData\Local\Temp\ipykernel_19920\2517032948.py:11: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
Países de Eastern Europe: ['Afghanistan' 'Africa' 'Albania' 'Algeria' 'America' 'Andorra' 'Angola' 'Anguilla' 'Antarctica' 'Antigua and Barbuda' 'Argentina' 'Armenia' 'Aruba' 'Asia' 'Australia' 'Australia y Nueva Zelanda' 'Austria' 'Azerbaijan' 'Bahamas' 'Bahrain' 'Bangladesh' 'Barbados' 'Belarus' 'Belgium' 'Belize' 'Benin' 'Bermuda' 'Bhutan' 'Bolivia, Plurinational State of' 'Bosnia and Herzegovina' 'Botswana' 'Brazil' 'Brunei Darussalam' 'Bulgaria' 'Burkina Faso' 'Burundi' 'Cabo Verde' 'Cambodia' 'Cameroon' 'Canada' 'Caribbean' 'Cayman Islands' 'Central Africa' 'Central African Republic' 'Central America' 'Central Asia' 'Chad' 'Chequia and Slovaquia' 'Chile' 'China' 'Colombia' 'Comoros' 'Congo' 'Congo, Democratic Republic of the' 'Costa Rica' 'Croatia' 'Cuba' 'Cyprus' 'Czechia' "Côte d'Ivoire" 'Denmark' 'Djibouti' 'Dominica' 'Dominican Republic' 'EFTA countries and others' 'Eastern Africa' 'Eastern Asia' 'Ecuador' 'Egypt' 'El Salvador' 'Equatorial Guinea' 'Eritrea' 'Estonia' 'Eswatini' 'Ethiopia' 'Europe' 'Ex Soviet Union' 'Ex Unión Soviética' 'Falkland Islands (Malvinas)' 'Faroe Islands' 'Fiji' 'Finland' 'France' 'French Polynesia' 'Gabon' 'Gambia' 'Georgia' 'Germany' 'Ghana' 'Gibraltar' 'Greece' 'Greenland' 'Grenada' 'Guatemala' 'Guernsey' 'Guinea' 'Guinea-Bissau' 'Guyana' 'Haiti' 'Holy See' 'Honduras' 'Hungary' 'Iceland' 'India' 'Indonesia' 'Iran, Islamic Republic of' 'Iraq' 'Ireland' 'Isle of Man' 'Israel' 'Italy' 'Jamaica' 'Japan' 'Jersey' 'Jordan' 'Kazakhstan' 'Kenya' 'Kiribati' "Korea, Democratic People's Republic of" 'Korea, Republic of' 'Kosovo' 'Kuwait' 'Kyrgyzstan' "Lao People's Democratic Republic" 'Latvia' 'Lebanon' 'Lesotho' 'Liberia' 'Libya' 'Liechtenstein' 'Lithuania' 'Luxembourg' 'Madagascar' 'Malawi' 'Malaysia' 'Maldives' 'Mali' 'Malta' 'Marshall Islands' 'Mauritania' 'Mauritius' 'Melanesia' 'Mexico' 'Micronesia' 'Micronesia, Federated States of' 'Moldova, Republic of' 'Monaco' 'Mongolia' 'Montenegro' 'Montserrat' 'Morocco' 'Mozambique' 'Myanmar' 'Nauru' 'Nepal' 'Netherlands, Kingdom of the' 'New Caledonia' 'New Zealand' 'Nicaragua' 'Niger' 'Nigeria' 'North Macedonia' 'Northern Africa' 'Northern America' 'Norway' 'Oceania' 'Oman' 'Other' 'Pakistan' 'Palau' 'Palestine, State of' 'Panama' 'Papua New Guinea' 'Paraguay' 'Peru' 'Philippines' 'Pitcairn' 'Poland' 'Portugal' 'Qatar' 'Romania' 'Russian Federation' 'Rwanda' 'Saint Barthélemy' 'Saint Helena, Ascension and Tristan da Cunha' 'Saint Kitts and Nevis' 'Saint Lucia' 'Saint Pierre and Miquelon' 'Saint Vincent and the Grenadines' 'Samoa' 'San Marino' 'Sao Tome and Principe' 'Saudi Arabia' 'Senegal' 'Serbia' 'Serbia and Montenegro' 'Seychelles' 'Sierra Leone' 'Singapore' 'Slovakia' 'Slovenia' 'Solomon Islands' 'Somalia' 'South Africa' 'South Sudan' 'Southeast Asia' 'Southern Africa' 'Southern America' 'Southern Asia' 'Spain' 'Sri Lanka' 'Sudan' 'Suriname' 'Sweden' 'Switzerland' 'Syrian Arab Republic' 'TOTAL' 'Taiwan' 'Taiwan, Province of China' 'Tajikistan' 'Tanzania, United Republic of' 'Thailand' 'Timor-Leste' 'Togo' 'Tonga' 'Trinidad and Tobago' 'Tunisia' 'Turkmenistan' 'Turks and Caicos Islands' 'Tuvalu' 'Türkiye' 'UNKNOWN' 'Uganda' 'Ukraine' 'United Arab Emirates' 'United Kingdom of Great Britain and Northern Ireland' 'United States of America' 'Uruguay' 'Uzbekistan' 'Vanuatu' 'Venezuela, Bolivarian Republic of' 'Viet Nam' 'Virgin Islands (British)' 'Wallis and Futuna' 'Western Africa' 'Western Asia' 'Western Sahara' 'Yemen' 'Zambia' 'Zimbabwe'] ['Afghanistan' 'Africa' 'Albania' 'Algeria' 'America' 'Andorra' 'Angola' 'Anguilla' 'Antarctica' 'Antigua and Barbuda' 'Argentina' 'Armenia' 'Aruba' 'Asia' 'Australia' 'Australia y Nueva Zelanda' 'Austria' 'Azerbaijan' 'Bahamas' 'Bahrain' 'Bangladesh' 'Barbados' 'Belarus' 'Belgium' 'Belize' 'Benin' 'Bermuda' 'Bhutan' 'Bolivia, Plurinational State of' 'Bosnia and Herzegovina' 'Botswana' 'Brazil' 'Brunei Darussalam' 'Bulgaria' 'Burkina Faso' 'Burundi' 'Cabo Verde' 'Cambodia' 'Cameroon' 'Canada' 'Caribbean' 'Cayman Islands' 'Central Africa' 'Central African Republic' 'Central America' 'Central Asia' 'Chad' 'Chequia and Slovaquia' 'Chile' 'China' 'Colombia' 'Comoros' 'Congo' 'Congo, Democratic Republic of the' 'Costa Rica' 'Croatia' 'Cuba' 'Cyprus' 'Czechia' "Côte d'Ivoire" 'Denmark' 'Djibouti' 'Dominica' 'Dominican Republic' 'EFTA countries and others' 'Eastern Africa' 'Eastern Asia' 'Ecuador' 'Egypt' 'El Salvador' 'Equatorial Guinea' 'Eritrea' 'Estonia' 'Eswatini' 'Ethiopia' 'Europe' 'Ex Soviet Union' 'Ex Unión Soviética' 'Falkland Islands (Malvinas)' 'Faroe Islands' 'Fiji' 'Finland' 'France' 'French Polynesia' 'Gabon' 'Gambia' 'Georgia' 'Germany' 'Ghana' 'Gibraltar' 'Greece' 'Greenland' 'Grenada' 'Guatemala' 'Guernsey' 'Guinea' 'Guinea-Bissau' 'Guyana' 'Haiti' 'Holy See' 'Honduras' 'Hungary' 'Iceland' 'India' 'Indonesia' 'Iran, Islamic Republic of' 'Iraq' 'Ireland' 'Isle of Man' 'Israel' 'Italy' 'Jamaica' 'Japan' 'Jersey' 'Jordan' 'Kazakhstan' 'Kenya' 'Kiribati' "Korea, Democratic People's Republic of" 'Korea, Republic of' 'Kosovo' 'Kuwait' 'Kyrgyzstan' "Lao People's Democratic Republic" 'Latvia' 'Lebanon' 'Lesotho' 'Liberia' 'Libya' 'Liechtenstein' 'Lithuania' 'Luxembourg' 'Madagascar' 'Malawi' 'Malaysia' 'Maldives' 'Mali' 'Malta' 'Marshall Islands' 'Mauritania' 'Mauritius' 'Melanesia' 'Mexico' 'Micronesia' 'Micronesia, Federated States of' 'Moldova, Republic of' 'Monaco' 'Mongolia' 'Montenegro' 'Montserrat' 'Morocco' 'Mozambique' 'Myanmar' 'Nauru' 'Nepal' 'Netherlands, Kingdom of the' 'New Caledonia' 'New Zealand' 'Nicaragua' 'Niger' 'Nigeria' 'North Macedonia' 'Northern Africa' 'Northern America' 'Norway' 'Oceania' 'Oman' 'Other' 'Pakistan' 'Palau' 'Palestine, State of' 'Panama' 'Papua New Guinea' 'Paraguay' 'Peru' 'Philippines' 'Pitcairn' 'Poland' 'Portugal' 'Qatar' 'Romania' 'Russian Federation' 'Rwanda' 'Saint Barthélemy' 'Saint Helena, Ascension and Tristan da Cunha' 'Saint Kitts and Nevis' 'Saint Lucia' 'Saint Pierre and Miquelon' 'Saint Vincent and the Grenadines' 'Samoa' 'San Marino' 'Sao Tome and Principe' 'Saudi Arabia' 'Senegal' 'Serbia' 'Serbia and Montenegro' 'Seychelles' 'Sierra Leone' 'Singapore' 'Slovakia' 'Slovenia' 'Solomon Islands' 'Somalia' 'South Africa' 'South Sudan' 'Southeast Asia' 'Southern Africa' 'Southern America' 'Southern Asia' 'Spain' 'Sri Lanka' 'Sudan' 'Suriname' 'Sweden' 'Switzerland' 'Syrian Arab Republic' 'TOTAL' 'Taiwan' 'Taiwan, Province of China' 'Tajikistan' 'Tanzania, United Republic of' 'Thailand' 'Timor-Leste' 'Togo' 'Tonga' 'Trinidad and Tobago' 'Tunisia' 'Turkmenistan' 'Turks and Caicos Islands' 'Tuvalu' 'Türkiye' 'UNKNOWN' 'Uganda' 'Ukraine' 'United Arab Emirates' 'United Kingdom of Great Britain and Northern Ireland' 'United States of America' 'Uruguay' 'Uzbekistan' 'Vanuatu' 'Venezuela, Bolivarian Republic of' 'Viet Nam' 'Virgin Islands (British)' 'Wallis and Futuna' 'Western Africa' 'Western Asia' 'Western Sahara' 'Yemen' 'Zambia' 'Zimbabwe']
import plotly.graph_objects as go
import pandas as pd
# datos agrupados por subregión y año
filtered_data_inmi_grouped = filtered_data_inmi.groupby(['reporting_country_name', 'year']).agg({'inmigrations_log': 'sum'}).reset_index()
# una figura de Plotly
fig = go.Figure()
# todas las subregiones únicas
subregions = filtered_data_inmi_grouped['reporting_country_name'].unique()
# Definiré una paleta de colores para daltonismo se haría de este modo aprox.
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
# una línea para cada subregión con colores definidos arriba
for i, subregion in enumerate(subregions):
# Filtro los datos por subregión
subregion_data = filtered_data_inmi_grouped[filtered_data_inmi_grouped['reporting_country_name'] == subregion]
# un color de la paleta a la subregión
color = colors[i % len(colors)] # Aseguramos así que no se repitan colores si hay más subregiones que colores
# Añado la línea para esa subregión
fig.add_trace(go.Scatter(
x=subregion_data['year'],
y=subregion_data['inmigrations_log'],
mode='lines+markers',
name=subregion,
line=dict(color=color, width=2), # Ajustar el grosor de la línea
marker=dict(size=8) # Tamaño de los puntos
))
# Personalizacion del gráfico
fig.update_layout(
title='Evolución de las Inmigraciones por Pais que reporta a lo largo del tiempo',
xaxis_title='Año',
yaxis_title='Inmigraciones (Log)',
template='plotly_white',
showlegend=True,
height=600,
width=1000
)
# Mostramos el gráfico
fig.show()
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Configuración de estilo
sns.set_theme(style="whitegrid")
# Filtrar o agrupar datos si es necesario (opcional)
# Por ejemplo: merged_data_unique_i = merged_data_unique_i.dropna(subset=['jan', 'reporting_country'])
# Crear el gráfico tipo ridge line
plt.figure(figsize=(12, 8))
sns.kdeplot(
data=merged_data_unique_i,
x="jan",
hue="reporting_country",
multiple="stack", # Alternativamente: 'fill' o 'layer'
palette="muted"
)
plt.title("Ridge Line Plot de la medida 'jan' por 'reporting_country'")
plt.xlabel("jan")
plt.ylabel("Densidad")
plt.show()
Por los histogramas y sus superposiciones podemos ver la distinción de los tipos más claramente en el caso del análisis por la variable alcohol donde el tipo 1 al menos se distingue del resto, también en color_intensity sucede con el tipo 2.
class0 = dfwine[dfwine.target == 0]
class1 = dfwine[dfwine.target == 1]
class2 = dfwine[dfwine.target == 2]
fig, axs = plt.subplots(1, len(feats_to_explore), figsize=(15, 5))
# Un Histograma por variable
for i, col in enumerate(feats_to_explore):
axs[i].hist(class0[col], alpha=0.4, color='tab:blue', label='Tipo 0')
axs[i].hist(class1[col], alpha=0.4, color='tab:olive', label='Tipo 1')
axs[i].hist(class2[col], alpha=0.4, color='tab:orange', label='Tipo 2')
axs[i].set_title(f'Histograma de {col}')
axs[i].legend() # leyenda
axs[i].axvline(class0[col].mean(), color='tab:blue', linestyle='dashed', linewidth=1)
axs[i].axvline(class1[col].mean(), color='tab:olive', linestyle='dashed', linewidth=1)
axs[i].axvline(class2[col].mean(), color='tab:orange', linestyle='dashed', linewidth=1)
# No superponer los diferentes histogramas de variables diferentes
plt.tight_layout()
# Mostrar los histogramas
plt.show()
#calculamos la correlacion usando spearman
corrmat = dfwine[feats_to_explore].corr(method='spearman')
print(corrmat)
alcohol magnesium color_intensity alcohol 1.000000 0.365503 0.635425 magnesium 0.365503 1.000000 0.357029 color_intensity 0.635425 0.357029 1.000000
#visualizamos con un heatmap
f, ax = plt.subplots(figsize=(5, 5))
sns.heatmap(corrmat, ax=ax, cmap="YlGnBu", linewidths=0.1)
<AxesSubplot:>
#visualicemos
corrdf=dfwine[['alcohol','magnesium','color_intensity','target']]
sns.pairplot(corrdf,hue='target')
<seaborn.axisgrid.PairGrid at 0x14211ea4130>
from mpl_toolkits import mplot3d
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(221, projection='3d')
alpha=0.4
ax.scatter(corrdf['alcohol'], corrdf['magnesium'], corrdf['color_intensity'], c=corrdf['target'],cmap='viridis', alpha=alpha)
ax.set_xlabel('alcohol')
ax.set_ylabel('magnesium')
ax.set_zlabel('color')
ax = fig.add_subplot(222)
ax.scatter(corrdf['alcohol'], corrdf['magnesium'], c=corrdf['target'], cmap='viridis', alpha=alpha)
ax.set_xlabel('alcohol')
ax.set_ylabel('magnesium')
ax = fig.add_subplot(223)
ax.scatter(corrdf['alcohol'], corrdf['color_intensity'], c=corrdf['target'], cmap='viridis', alpha=alpha)
ax.set_xlabel('alcohol')
ax.set_ylabel('color')
ax = fig.add_subplot(224)
ax.scatter(corrdf['magnesium'], corrdf['color_intensity'], c=corrdf['target'], cmap='viridis', alpha=alpha)
ax.set_xlabel('magnesium')
ax.set_ylabel('color')
Text(0, 0.5, 'color')
La correlación más fuerte se da entre color_intensity y alcohol de 0.63 y coincide con el gráfico de correlación donde se percibe una correlación positiva para los tres tipos de vino ya que los puntos siguen una distribución lineal que indica que cuando aumenta el alcohol aumenta el color_intensity. En el heatmap también vemos más obscuro coloreada la intersección entre estas dos variables.
Una vez analizados los atributos descriptivos, es el momento de prepararlos para que nos sean útiles de cara a predecir valores. En este apartado:
from sklearn.preprocessing import StandardScaler
#variables descriptivas
descriptivos = dfwine.iloc[:,:-1]
#instanciamos standarscaler
scaler = StandardScaler()
#aplicamos la transformación
descriptivos_escalados = scaler.fit_transform(descriptivos)
#valores antes de reemplazar atributos en el dataset
dfwine.head()
| alcohol | malic_acid | ash | alcalinity_of_ash | magnesium | total_phenols | flavanoids | nonflavanoid_phenols | proanthocyanins | color_intensity | hue | od280/od315_of_diluted_wines | proline | target | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 14.23 | 1.71 | 2.43 | 15.6 | 127.0 | 2.80 | 3.06 | 0.28 | 2.29 | 5.64 | 1.04 | 3.92 | 1065.0 | 0 |
| 1 | 13.20 | 1.78 | 2.14 | 11.2 | 100.0 | 2.65 | 2.76 | 0.26 | 1.28 | 4.38 | 1.05 | 3.40 | 1050.0 | 0 |
| 2 | 13.16 | 2.36 | 2.67 | 18.6 | 101.0 | 2.80 | 3.24 | 0.30 | 2.81 | 5.68 | 1.03 | 3.17 | 1185.0 | 0 |
| 3 | 14.37 | 1.95 | 2.50 | 16.8 | 113.0 | 3.85 | 3.49 | 0.24 | 2.18 | 7.80 | 0.86 | 3.45 | 1480.0 | 0 |
| 4 | 13.24 | 2.59 | 2.87 | 21.0 | 118.0 | 2.80 | 2.69 | 0.39 | 1.82 | 4.32 | 1.04 | 2.93 | 735.0 | 0 |
dfwine.iloc[:,:-1] = descriptivos_escalados
#después de escalar
dfwine.head()
| alcohol | malic_acid | ash | alcalinity_of_ash | magnesium | total_phenols | flavanoids | nonflavanoid_phenols | proanthocyanins | color_intensity | hue | od280/od315_of_diluted_wines | proline | target | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1.518613 | -0.562250 | 0.232053 | -1.169593 | 1.913905 | 0.808997 | 1.034819 | -0.659563 | 1.224884 | 0.251717 | 0.362177 | 1.847920 | 1.013009 | 0 |
| 1 | 0.246290 | -0.499413 | -0.827996 | -2.490847 | 0.018145 | 0.568648 | 0.733629 | -0.820719 | -0.544721 | -0.293321 | 0.406051 | 1.113449 | 0.965242 | 0 |
| 2 | 0.196879 | 0.021231 | 1.109334 | -0.268738 | 0.088358 | 0.808997 | 1.215533 | -0.498407 | 2.135968 | 0.269020 | 0.318304 | 0.788587 | 1.395148 | 0 |
| 3 | 1.691550 | -0.346811 | 0.487926 | -0.809251 | 0.930918 | 2.491446 | 1.466525 | -0.981875 | 1.032155 | 1.186068 | -0.427544 | 1.184071 | 2.334574 | 0 |
| 4 | 0.295700 | 0.227694 | 1.840403 | 0.451946 | 1.281985 | 0.808997 | 0.663351 | 0.226796 | 0.401404 | -0.319276 | 0.362177 | 0.449601 | -0.037874 | 0 |
from sklearn.model_selection import train_test_split
X = dfwine.drop(labels='target',axis=1)
y = dfwine.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=42)
La estandardización consiste en restar el cada instancia de un atributo de la media y dividirlo por la desviación estandar.
Si aplicamos esta técnica antes de separar la muestra en submuestra de entrenamiento y test, la media y la desviación estandar usadas para la estandardización son las de la muestra completa y contaminaríamos con esta información la muestra de test con la consecuencia de poder introducir sesgo.
Con la función StandardScaler, es mejor centrar y escalar de manera independiente en cada atributo computando las estadísticas relevantes en las muestras del entrenamiento. La media y la desviacion estandar entonces se guardan para usarlas después en otros datos para su transformación.
La estandardización permite convertir la distribución del dataset a una distribución normal estandard de media 0 y desviación estandar 1 que puede reconvertirse a su forma original.
Esta unificación de escala facilita la labor de los algoritmos de aprender los pesos de los atributos sin necesidad de tomar en cuenta rangos o escalas. En particular la estandardización es útil cuando existen outliers que pueden afectar a las escalas de los valores normalizados. Consigue mantener el valor de los outliers sin que impacten negativamente con sesgos y falta de rendimiento a los modelos.
Para algunos algoritmos de aprendizaje automático como Suport Vector Machine es imprescindible estandardizar.
Con el propósito de comprobar visualmente la distribución de la variable objetivo teniendo en cuenta todos los atributos descriptivos a la vez, vamos a reducir la dimensionalidad del problema a solamente dos atributos que serán la proyección de los atributos descriptivos originales.
from sklearn.decomposition import PCA
#X ya está escalado en el punto 3
pca = PCA(n_components=2)
X_w = pca.fit(X).transform(X)
target_names = winedata.target_names
# Percentage of variance explained for each components
print('explained variance ratio (first two components): %s' % str(pca.explained_variance_ratio_))
plt.figure()
colors = ['navy', 'turquoise', 'darkorange']
lw = 2
for color, i, target_name in zip(colors, [0, 1, 2], target_names):
plt.scatter(X_w[y == i, 0], X_w[y == i, 1], color=color, alpha=.8, lw=lw, label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('PCA of Wine dataset')
plt.show()
explained variance ratio (first two components): [0.36198848 0.1920749 ]
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, perplexity=50, learning_rate=10, n_iter=1000)
X_w3 = tsne.fit_transform(X)
X_w3.shape
plt.figure()
colors = ['navy', 'turquoise', 'darkorange']
lw = 2
for color, i, target_name in zip(colors, [0, 1, 2], target_names):
plt.scatter(X_w3[y == i, 0], X_w3[y == i, 1], color=color, alpha=.8, lw=lw, label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('TSNE Perplexity 50 LR 10 of Wine dataset')
plt.show()
# “The performance of SNE is fairly robust to changes in the perplexity, and typical values are between 5 and 50.”
# But the story is more nuanced than that.
# Getting the most from t-SNE may mean analyzing multiple plots with different perplexities. Perplexity <= number of points
# distances between well-separated clusters in a t-SNE plot may mean nothing
# learning rate (often called “epsilon”) of 10. the most important thing is to iterate until reaching a stable configuration.
# Bajo perplexity para potenciar estructuras locales - los patrones o relaciones que existen entre puntos de datos cercanos entre sí en el espacio de alta dimensión
# se vuelve inestable
tsne = TSNE(n_components=2, perplexity=60, learning_rate=10, n_iter=500)
X_w3 = tsne.fit_transform(X)
X_w3.shape
plt.figure()
colors = ['navy', 'turquoise', 'darkorange']
lw = 2
for color, i, target_name in zip(colors, [0, 1, 2], target_names):
plt.scatter(X_w3[y == i, 0], X_w3[y == i, 1], color=color, alpha=.8, lw=lw, label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('TSNE Perplexity 10 LR 30 of Wine dataset')
plt.show()
Creo que la reducción ha funcionado bien pues los componentes principales obtenidos parecen contener suficiente varianza como para permitir visualizar las 3 clases.
En cuanto a los métodos creo que el PCA es más claro y estable.
Aunque en el primer plot de TSNE en el que utilizo Perplexity 50 y learning rate 10 los cluster se visualizan mejor. El perplexity más bajo potencia las estructuras locales contra las globales, así se aprecian más los patrones entre puntos cercanos. Variar estos parámetros resulta muy complejo y la visualización puede cambiar drásticamente al aumentar la perplexity.
En los problemas de clasificación, es muy común encontrar conjuntos de datos muy desbalanceados. En la industria existen múltiples ejemplos, como la detección de fraude o la fuga de clientes. Por este motivo, este ejercicio se centra en el análisis de este tipo de conjuntos.
Vamos a utilizar un conjunto de datos simplificado del data set Turbo Engine del NASA Prognostics Center of Excellence Data Set Repository, el cual sólo estará formado por dos características explicativas, y la variable objetivo, para así poder analizar visualmente el problema de manera sencilla. En este data set tenemos mediciones de los sensores de un motor cada cierto tiempo, siendo la variable objetivo el estado del motor, el cual indica si hay avería o no en el instante de la medición.
Vamos a comenzar cargando el conjunto de datos:
engine_df = pd.read_csv('Turbo_engine.csv', sep=';')
target_feat = 'y'
x1_feat = 'x_1'
x2_feat = 'x_2'
engine_df.head()
| x_1 | x_2 | y | |
|---|---|---|---|
| 0 | 1 | 64182 | 0 |
| 1 | 2 | 64215 | 0 |
| 2 | 3 | 64235 | 0 |
| 3 | 4 | 64235 | 0 |
| 4 | 5 | 64237 | 0 |
A continuación, vamos a analizar la distribución de nuestro conjunto de datos. Para ello, utilizaremos la función show_distribution:
def show_distribution(df):
freq = df[target_feat].value_counts()
plt.pie(freq, labels=('No engine failure ('+str(freq[0])+')', 'Engine failure ('+str(freq[1])+')'), autopct='%1.1f%%')
plt.title("Engine failure distribution")
show_distribution(engine_df)
Cómo se puede observar, el conjunto está muy desbalanceado, ya que sólo 0.5% de las muestras se corresponden con una situación de avería en el motor.
Aprovechando que sólo tenemos dos características descriptivas, vamos a mostrar mediante un scatter plot nuestro conjunto de datos. Para ello utilizaremos la función plot_data. Esta función recibe tres parámetros:
def plot_data(data_sets, only_failures=False, cmap='Paired'):
if not isinstance(data_sets, list):
data_sets = [data_sets]
colors = np.array(["skyblue", "red"])
fig, ax = plt.subplots(len(data_sets), 1, figsize=(12, 7 * len(data_sets)))
for i, data in enumerate(data_sets):
data = data if not only_failures \
else data[data[target_feat] == 1]
eff_ax = ax if len(data_sets) == 1 else ax[i]
X = data[[x1_feat, x2_feat]].values
y = data[target_feat].values
if not only_failures:
clf = LinearDiscriminantAnalysis()
clf.fit(X, y)
h = 2
x_min, x_max = X[:,0].min() - 10*h, X[:,0].max() + 10*h
y_min, y_max = X[:,1].min() - 10*h, X[:,1].max() + 10*h
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
eff_ax.contourf(xx, yy, Z, cmap=cmap, alpha=0.25)
eff_ax.contour(xx, yy, Z, colors='black', linewidths=0.7)
eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
plot_data([engine_df], only_failures=False)
En la imagen anterior, se puede observar la distribución de nuestro conjunto de datos. Cómo ya habíamos analizado, hay muy pocas averías de motor (puntos rojos). En la imagen, además, se puede ver la frontera de decisión del clasificador base. Esta frontera nos permite ver las áreas que el modelo considera que son de una clase y las que considera que son de otra. Al poner encima los puntos vemos si los clasifica correctamente en el área que les corresponde. En este caso, la frontera divide el conjunto de datos en dos partes, teniendo sólo dos puntos en una de ellas (avería de motor, parte superior).
El clasificar hace esta división porque no generaliza bien ya que la clase 1 (avería) no está suficientemente representada en el conjunto de entremaniento. El clasificador hace overfitting en esta clase y la clasifica incorrectamente. La métrica que nos indicaría claramente la existencia de overfitting es en el caso de ser la clase avería (1) la negativa: la specificity o tasa de instancias correctamente clasificadas como negativas respecto a todas las instancias negativas. Si en cambio la clase positiva es la clase avería 1 la métrica que usaríamos sería el recall o sensitivity que se corresponden con la tasa de verdaderos positivos (TPR.)
Para abordar el problema de datos desbalanceados, vamos a analizar la técnica de sobremuestreo (oversampling) de la clase minoritaria. En la literatura hay más técnicas para abordar este problema, como el submuestreo (undersampling) de la clase mayoritaria, pero en esta PEC nos vamos a centrar sólo en esta técnica.
from collections import Counter
#X, y = make_classification(n_classes=2, class_sep=2, weights=[0.1, 0.9], n_informative=3, n_redundant=1, flip_y=0, n_features=20, n_clusters_per_class=1, n_samples=1000, random_state=10)
X = engine_df[[x1_feat, x2_feat]].values
y = engine_df[target_feat].values
#shape original
print("Shape of data (número de filas):", engine_df.shape[0] , "\n")
print('Original dataset shape %s' % Counter(y))
#Duplicación aleatoria (random over-sampling), fijando random_state=10
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=10)
X_ros, y_ros = ros.fit_resample(X, y)
print('Resampled dataset shape random %s' % Counter(y_res))
#creamos un dataframe pandas de nuestro array numpy
engine_df_ros = pd.DataFrame(data=X_ros, columns=['x_1','x_2'])
engine_df_ros['y']=y_ros
show_distribution(engine_df_ros)
#SMOTE (Synthetic Minority Over-sampling Technique), fijando random_state=10
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=10)
X_smote, y_smote = sm.fit_resample(X, y)
print('Resampled dataset shape con smote %s' % Counter(y_smote))
#creamos un dataframe pandas de nuestro array numpy
engine_df_smote = pd.DataFrame(data=X_smote, columns=['x_1','x_2'])
engine_df_smote['y']=y_smote
#ADASYN (Adaptive Synthetic Sampling), fijando random_state=10
from imblearn.over_sampling import ADASYN
ada = ADASYN(random_state=10)
X_ada, y_ada = ada.fit_resample(X, y)
print('Resampled dataset shape con adasyn %s' % Counter(y_ada))
#creamos un dataframe pandas de nuestro array numpy
engine_df_ada = pd.DataFrame(data=X_ada, columns=['x_1','x_2'])
engine_df_ada['y']=y_ada
Shape of data (número de filas): 20631
Original dataset shape Counter({0: 20531, 1: 100})
Resampled dataset shape random Counter({0: 20531, 1: 20531})
Resampled dataset shape con smote Counter({0: 20531, 1: 20531})
Resampled dataset shape con adasyn Counter({1: 20565, 0: 20531})
show_distribution(engine_df_smote)
show_distribution(engine_df_ada)
plot_data([engine_df,engine_df_ros], only_failures=True)
No se aprecian diferencias en los datasets puesto que la duplicación aleatoria no cambia la distribución suficientemente y los datos sintéticos aparecen superpuestos a los originales.
plot_data([engine_df,engine_df_ros], only_failures=False)
La frontera de la decisión se ha ajustado y las clases avería o negativa tienen son clasificadas correctamente aunque la consecuencia es que hay muchas clases 0 clasificadas ahora incorrectamente.
plot_data([engine_df_ros,engine_df_smote,engine_df_ada], only_failures=True)
Como hemos visto la duplicación aleatoria no modifica cómo se visualizan la clase pues se superpone. En cambio tanto en el oversampling usando SMOTE como ADASYN vemos que los nuevos datos sintéticos siguen una distribución lineal en ambos casos sin percibirse diferencia.
En SMOTE se calcula el dato sintético usando un método lineal, usando la distancia de un punto con sus k vecinos y ponderando esta distancia para generar el nuevo dato sintético.
ADASYN genera nuevos datos sintéticos de forma sistemática y adaptativa y no intrínsecamente lineal y basándose en la densidad de la clase, creando más datos sintéticos allí donde hay menos densidad y es más dificil clasificar la clase minoritaria.
No se visualizan diferencias entre la visualización de SMOTE y ADASYN puesto que ambas se basan en crear los datos sintéticos en las zonas fronterizas de la clase minoritaria para evitar el solape pero en ninguna se realiza penalización sobre las ubicaciones que se superponen o quedan rodeadas de puntos de la clase mayoritaria.
plot_data([engine_df_ros,engine_df_smote,engine_df_ada], only_failures=False)
No se ha modificado la linea de decisión usando oversampling con SMOTE ni ADASYN. Obtenemos el mismo resultado que con la duplicación aleatoria. Esto se debe a que los nuevos datos sintéticos se superponen o están demasiado cercanos a los de la clase mayoritaria y se considerarían ruido.
El clasificador usado LDA utiliza la media de cada clase, la dispersión intraclase e interclase, utiliza la relación entre estas dos dispersiones para pintar el eje que mejor separa las clases donde después proyectará las nuevas instancias.
Es posible que las nuevas instancias sintéticas creadas con SMOTE y ADASYN al estar superpuestas con instancias de la clase mayoritaria no estén aportando más que lo ya aportado por la duplicación aleatoria a la separación de clases pues la distribución interclase no varía suficientemente. La distribución interclases no cambia suficientemente.
LDA presupone una distribución de las clases son gaussianas y con covarianza similares, pero si no se cumplen estas características o las clases están superpuestas como es este el caso, este algoritmo no es suficientemente sensible para separar las clases correctamente.
Las tecnicas de oversampling como SMOTE que sirven para balancear el conjunto de datos se deben aplicar después de dividir el conjunto de datos en entrenamiento/validación/test.
Esto se debe a que queremos balancear el conjunto de entrenamiento para que el modelo pueda aprender bien con suficientes ejemplos de las dos clases y pueda generalizar.
Pero si aplicamos el oversampling antes de dividir los datos corremos el riesgo de tener en el conjunto de test datos sintéticos y duplicados del conjunto de training con lo que la evaluación del modelo quedaría comprometida.
En el caso de este ejercicio, puesto que no vamos a evaluar el modelo más que con una visualización, no ha sido necesario realizar la separación del dataset en training/test.